use crate::document::DocumentManager;
use tower_lsp::lsp_types::{
    DocumentFormattingParams, DocumentRangeFormattingParams, Position, Range, TextEdit, FormattingOptions,
    CodeActionParams, CodeAction, CodeActionKind, WorkspaceEdit, CodeActionResponse, CodeActionOrCommand,
};
use std::sync::Arc;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
use tracing::{debug, warn};
use regex::Regex;

/// Cache entry for formatting results
#[derive(Debug, Clone)]
struct FormattingCacheEntry {
    formatted_content: String,
    timestamp: Instant,
    content_hash: u64,
}

/// Formatting cache with TTL
struct FormattingCache {
    entries: HashMap<String, FormattingCacheEntry>,
    ttl: Duration,
}

impl FormattingCache {
    fn new(ttl: Duration) -> Self {
        Self {
            entries: HashMap::new(),
            ttl,
        }
    }

    fn get(&self, key: &str, content_hash: u64) -> Option<String> {
        if let Some(entry) = self.entries.get(key) {
            if entry.timestamp.elapsed() < self.ttl && entry.content_hash == content_hash {
                return Some(entry.formatted_content.clone());
            }
        }
        None
    }

    fn insert(&mut self, key: String, formatted_content: String, content_hash: u64) {
        let entry = FormattingCacheEntry {
            formatted_content,
            timestamp: Instant::now(),
            content_hash,
        };
        self.entries.insert(key, entry);
    }

    fn clear(&mut self) {
        self.entries.clear();
    }
}

/// Enhanced formatting provider with intelligent formatting and refactoring capabilities
pub struct FormattingProvider {
    document_manager: Arc<DocumentManager>,
    cache: Arc<Mutex<FormattingCache>>,
    // Pre-compiled regex patterns for formatting rules
    formatting_patterns: HashMap<String, Vec<(Regex, String)>>,
}

impl FormattingProvider {
    pub fn new(document_manager: Arc<DocumentManager>) -> Self {
        let cache = Arc::new(Mutex::new(FormattingCache::new(Duration::from_secs(60)))); // 1 minute TTL

        // Pre-compile formatting patterns for different languages
        let mut formatting_patterns = HashMap::new();

        // Python formatting patterns
        let mut python_patterns = Vec::new();
        python_patterns.push((
            Regex::new(r"(\w+)\s*=\s*(\w+)").unwrap(),
            "$1 = $2".to_string(), // Normalize spacing around assignment
        ));
        python_patterns.push((
            Regex::new(r"if\s*\(\s*(.+?)\s*\)\s*:").unwrap(),
            "if $1:".to_string(), // Remove unnecessary parentheses in if statements
        ));
        python_patterns.push((
            Regex::new(r"(\w+)\s*\(\s*(.+?)\s*\)").unwrap(),
            "$1($2)".to_string(), // Normalize function call spacing
        ));
        formatting_patterns.insert("python".to_string(), python_patterns);

        // JavaScript/TypeScript formatting patterns
        let mut js_patterns = Vec::new();
        js_patterns.push((
            Regex::new(r"(\w+)\s*=\s*(\w+)").unwrap(),
            "$1 = $2".to_string(), // Normalize spacing around assignment
        ));
        js_patterns.push((
            Regex::new(r"if\s*\(\s*(.+?)\s*\)\s*\{").unwrap(),
            "if ($1) {".to_string(), // Normalize if statement formatting
        ));
        js_patterns.push((
            Regex::new(r"function\s+(\w+)\s*\(\s*(.+?)\s*\)\s*\{").unwrap(),
            "function $1($2) {".to_string(), // Normalize function declaration
        ));
        formatting_patterns.insert("javascript".to_string(), js_patterns.clone());
        formatting_patterns.insert("typescript".to_string(), js_patterns);

        // Rust formatting patterns
        let mut rust_patterns = Vec::new();
        rust_patterns.push((
            Regex::new(r"let\s+(\w+)\s*=\s*(.+);").unwrap(),
            "let $1 = $2;".to_string(), // Normalize let bindings
        ));
        rust_patterns.push((
            Regex::new(r"fn\s+(\w+)\s*\(\s*(.+?)\s*\)\s*\{").unwrap(),
            "fn $1($2) {".to_string(), // Normalize function definitions
        ));
        formatting_patterns.insert("rust".to_string(), rust_patterns);

        Self {
            document_manager,
            cache,
            formatting_patterns,
        }
    }

    pub async fn format_document(
        &self,
        params: &DocumentFormattingParams,
    ) -> Result<Option<Vec<TextEdit>>, String> {
        let start_time = Instant::now();
        let uri = &params.text_document.uri;

        debug!("Formatting document: {}", uri);

        // Get document
        let document = match self.document_manager.get_document(uri) {
            Some(doc) => doc,
            None => {
                warn!("Document not found for formatting: {}", uri);
                return Ok(None);
            }
        };

        let (language, content, end_position, content_hash) = {
            let doc = document.read().await;
            let language = match &doc.language {
                Some(lang) => lang.clone(),
                None => {
                    debug!("No language detected for formatting");
                    return Ok(None);
                }
            };

            let content = doc.content.clone();
            let content_hash = self.compute_content_hash(&content);
            let end_position = doc.offset_to_position(doc.content.len())
                .map_err(|e| format!("Failed to calculate end position: {}", e))?;

            (language, content, end_position, content_hash)
        };

        // Check cache first
        let cache_key = format!("{}:{}:{}", uri, language, self.format_options_hash(&params.options));
        {
            let cache = self.cache.lock().await;
            if let Some(cached_content) = cache.get(&cache_key, content_hash) {
                debug!(
                    "Cache hit for formatting '{}' ({}ms)",
                    uri,
                    start_time.elapsed().as_millis()
                );

                if cached_content == content {
                    return Ok(None);
                } else {
                    let start = Position::new(0, 0);
                    return Ok(Some(vec![TextEdit {
                        range: Range::new(start, end_position),
                        new_text: cached_content,
                    }]));
                }
            }
        }

        // Format the entire document
        let formatted_content = self.format_content(&content, &language, &params.options)?;

        // Cache the result
        {
            let mut cache = self.cache.lock().await;
            cache.insert(cache_key, formatted_content.clone(), content_hash);
        }

        let elapsed = start_time.elapsed();
        debug!(
            "Document formatted in {}ms",
            elapsed.as_millis()
        );

        if formatted_content == content {
            // No changes needed
            Ok(None)
        } else {
            // Return a single edit that replaces the entire document
            let start = Position::new(0, 0);

            Ok(Some(vec![TextEdit {
                range: Range::new(start, end_position),
                new_text: formatted_content,
            }]))
        }
    }

    pub async fn format_range(
        &self,
        params: &DocumentRangeFormattingParams,
    ) -> Result<Option<Vec<TextEdit>>, String> {
        let uri = &params.text_document.uri;
        let range = params.range;

        debug!("Formatting range in document: {} ({:?})", uri, range);

        // Get document
        let document = match self.document_manager.get_document(uri) {
            Some(doc) => doc,
            None => {
                warn!("Document not found for range formatting: {}", uri);
                return Ok(None);
            }
        };

        let (language, range_text) = {
            let doc = document.read().await;
            let language = match &doc.language {
                Some(lang) => lang.clone(),
                None => {
                    debug!("No language detected for range formatting");
                    return Ok(None);
                }
            };

            // Extract the text in the range
            let range_text = doc.get_text_in_range(range)
                .map_err(|e| format!("Failed to get text in range: {}", e))?;

            (language, range_text)
        };

        // Format the range text
        let formatted_text = self.format_content(&range_text, &language, &params.options)?;

        if formatted_text == range_text {
            // No changes needed
            Ok(None)
        } else {
            Ok(Some(vec![TextEdit {
                range,
                new_text: formatted_text,
            }]))
        }
    }

    /// Compute a simple hash for content to use in caching
    fn compute_content_hash(&self, content: &str) -> u64 {
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};

        let mut hasher = DefaultHasher::new();
        content.hash(&mut hasher);
        hasher.finish()
    }

    /// Compute a hash for formatting options
    fn format_options_hash(&self, options: &FormattingOptions) -> u64 {
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};

        let mut hasher = DefaultHasher::new();
        options.tab_size.hash(&mut hasher);
        options.insert_spaces.hash(&mut hasher);
        hasher.finish()
    }

    /// Provide code actions for refactoring and quick fixes
    pub async fn provide_code_actions(
        &self,
        params: &CodeActionParams,
    ) -> Result<Option<CodeActionResponse>, String> {
        let uri = &params.text_document.uri;
        let range = params.range;

        debug!("Providing code actions for {} at {:?}", uri, range);

        // Get document
        let document = match self.document_manager.get_document(uri) {
            Some(doc) => doc,
            None => {
                warn!("Document not found for code actions: {}", uri);
                return Ok(None);
            }
        };

        let (language, content) = {
            let doc = document.read().await;
            let language = match &doc.language {
                Some(lang) => lang.clone(),
                None => {
                    debug!("No language detected for code actions");
                    return Ok(None);
                }
            };

            (language, doc.content.clone())
        };

        let mut actions = Vec::new();

        // Add language-specific code actions
        match language.as_str() {
            "python" => {
                actions.extend(self.get_python_code_actions(&content, range, uri)?);
            }
            "javascript" | "typescript" => {
                actions.extend(self.get_javascript_code_actions(&content, range, uri)?);
            }
            "rust" => {
                actions.extend(self.get_rust_code_actions(&content, range, uri)?);
            }
            _ => {}
        }

        // Add generic code actions
        actions.extend(self.get_generic_code_actions(&content, range, uri)?);

        if actions.is_empty() {
            Ok(None)
        } else {
            Ok(Some(actions.into_iter().map(CodeActionOrCommand::CodeAction).collect()))
        }
    }

    fn format_content(
        &self,
        content: &str,
        language: &str,
        options: &FormattingOptions,
    ) -> Result<String, String> {
        // Apply pattern-based formatting first
        let mut formatted = content.to_string();

        if let Some(patterns) = self.formatting_patterns.get(language) {
            for (pattern, replacement) in patterns {
                formatted = pattern.replace_all(&formatted, replacement.as_str()).to_string();
            }
        }

        // Then apply language-specific formatting
        match language {
            "python" => self.format_python(&formatted, options),
            "javascript" | "typescript" => self.format_javascript(&formatted, options),
            "rust" => self.format_rust(&formatted, options),
            _ => self.format_generic(&formatted, options),
        }
    }

    fn format_python(&self, content: &str, options: &FormattingOptions) -> Result<String, String> {
        let mut formatted_lines = Vec::new();
        let lines: Vec<&str> = content.lines().collect();

        for line in lines {
            let trimmed = line.trim();
            
            // Skip empty lines
            if trimmed.is_empty() {
                formatted_lines.push(String::new());
                continue;
            }

            // Calculate indentation level
            let indent_level = self.calculate_python_indent_level(trimmed);
            let indent = self.create_indent(indent_level, options);

            // Format the line
            let formatted_line = format!("{}{}", indent, trimmed);
            formatted_lines.push(formatted_line);
        }

        Ok(formatted_lines.join("\n"))
    }

    fn format_javascript(&self, content: &str, options: &FormattingOptions) -> Result<String, String> {
        let mut formatted_lines = Vec::new();
        let lines: Vec<&str> = content.lines().collect();
        let mut brace_level: i32 = 0;

        for line in lines {
            let trimmed = line.trim();
            
            // Skip empty lines
            if trimmed.is_empty() {
                formatted_lines.push(String::new());
                continue;
            }

            // Adjust brace level for closing braces
            if trimmed.starts_with('}') {
                brace_level = brace_level.saturating_sub(1);
            }

            // Create indentation
            let indent = self.create_indent(brace_level as usize, options);
            let formatted_line = format!("{}{}", indent, trimmed);
            formatted_lines.push(formatted_line);

            // Adjust brace level for opening braces
            if trimmed.ends_with('{') {
                brace_level += 1;
            }
        }

        Ok(formatted_lines.join("\n"))
    }

    fn format_rust(&self, content: &str, options: &FormattingOptions) -> Result<String, String> {
        // For Rust, we would typically use rustfmt
        // For now, provide basic formatting similar to JavaScript
        self.format_javascript(content, options)
    }

    fn format_generic(&self, content: &str, _options: &FormattingOptions) -> Result<String, String> {
        let mut formatted_lines = Vec::new();
        let lines: Vec<&str> = content.lines().collect();

        for line in lines {
            let trimmed = line.trim();
            
            // Skip empty lines
            if trimmed.is_empty() {
                formatted_lines.push(String::new());
                continue;
            }

            // For generic formatting, just normalize whitespace
            formatted_lines.push(trimmed.to_string());
        }

        Ok(formatted_lines.join("\n"))
    }

    fn calculate_python_indent_level(&self, line: &str) -> usize {
        // Simple heuristic for Python indentation
        if line.starts_with("def ") || line.starts_with("class ") {
            0
        } else if line.starts_with("if ") || line.starts_with("for ") || line.starts_with("while ") ||
                  line.starts_with("try:") || line.starts_with("except ") || line.starts_with("finally:") ||
                  line.starts_with("with ") {
            0
        } else if line.starts_with("elif ") || line.starts_with("else:") {
            0
        } else {
            1 // Default indentation for function/class body
        }
    }

    fn create_indent(&self, level: usize, options: &FormattingOptions) -> String {
        if options.insert_spaces {
            " ".repeat(level * options.tab_size as usize)
        } else {
            "\t".repeat(level)
        }
    }

    /// Get Python-specific code actions
    fn get_python_code_actions(
        &self,
        content: &str,
        range: Range,
        uri: &tower_lsp::lsp_types::Url,
    ) -> Result<Vec<CodeAction>, String> {
        let mut actions = Vec::new();
        let lines: Vec<&str> = content.lines().collect();

        // Check if the range contains a function that can be extracted
        let start_line = range.start.line as usize;
        let end_line = range.end.line as usize;

        if start_line < lines.len() && end_line < lines.len() {
            let selected_text: String = lines[start_line..=end_line].join("\n");

            // Extract method refactoring
            if selected_text.lines().count() > 3 && !selected_text.trim().starts_with("def ") {
                let mut edit_map = HashMap::new();
                edit_map.insert(uri.clone(), vec![TextEdit {
                    range,
                    new_text: "    extracted_method()".to_string(),
                }]);

                // Add the extracted method at the end of the class/file
                let method_definition = format!(
                    "\n\ndef extracted_method():\n{}",
                    selected_text.lines()
                        .map(|line| format!("    {}", line))
                        .collect::<Vec<_>>()
                        .join("\n")
                );

                edit_map.get_mut(uri).unwrap().push(TextEdit {
                    range: Range::new(
                        tower_lsp::lsp_types::Position::new(lines.len() as u32, 0),
                        tower_lsp::lsp_types::Position::new(lines.len() as u32, 0),
                    ),
                    new_text: method_definition,
                });

                actions.push(CodeAction {
                    title: "Extract Method".to_string(),
                    kind: Some(CodeActionKind::REFACTOR_EXTRACT),
                    diagnostics: None,
                    edit: Some(WorkspaceEdit {
                        changes: Some(edit_map),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(false),
                    disabled: None,
                    data: None,
                });
            }

            // Add import statement
            if selected_text.contains("os.") || selected_text.contains("sys.") {
                let import_line = if selected_text.contains("os.") { "import os\n" } else { "import sys\n" };

                let mut edit_map = HashMap::new();
                edit_map.insert(uri.clone(), vec![TextEdit {
                    range: Range::new(
                        tower_lsp::lsp_types::Position::new(0, 0),
                        tower_lsp::lsp_types::Position::new(0, 0),
                    ),
                    new_text: import_line.to_string(),
                }]);

                actions.push(CodeAction {
                    title: "Add missing import".to_string(),
                    kind: Some(CodeActionKind::QUICKFIX),
                    diagnostics: None,
                    edit: Some(WorkspaceEdit {
                        changes: Some(edit_map),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(true),
                    disabled: None,
                    data: None,
                });
            }
        }

        Ok(actions)
    }

    /// Get JavaScript/TypeScript-specific code actions
    fn get_javascript_code_actions(
        &self,
        content: &str,
        range: Range,
        uri: &tower_lsp::lsp_types::Url,
    ) -> Result<Vec<CodeAction>, String> {
        let mut actions = Vec::new();
        let lines: Vec<&str> = content.lines().collect();

        let start_line = range.start.line as usize;
        let end_line = range.end.line as usize;

        if start_line < lines.len() && end_line < lines.len() {
            let selected_text: String = lines[start_line..=end_line].join("\n");

            // Convert var to let/const
            if selected_text.contains("var ") {
                let converted = selected_text.replace("var ", "const ");

                let mut edit_map = HashMap::new();
                edit_map.insert(uri.clone(), vec![TextEdit {
                    range,
                    new_text: converted,
                }]);

                actions.push(CodeAction {
                    title: "Convert var to const".to_string(),
                    kind: Some(CodeActionKind::QUICKFIX),
                    diagnostics: None,
                    edit: Some(WorkspaceEdit {
                        changes: Some(edit_map),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(true),
                    disabled: None,
                    data: None,
                });
            }

            // Convert == to ===
            if selected_text.contains("==") && !selected_text.contains("===") {
                let converted = selected_text.replace("==", "===");

                let mut edit_map = HashMap::new();
                edit_map.insert(uri.clone(), vec![TextEdit {
                    range,
                    new_text: converted,
                }]);

                actions.push(CodeAction {
                    title: "Use strict equality (===)".to_string(),
                    kind: Some(CodeActionKind::QUICKFIX),
                    diagnostics: None,
                    edit: Some(WorkspaceEdit {
                        changes: Some(edit_map),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(true),
                    disabled: None,
                    data: None,
                });
            }
        }

        Ok(actions)
    }

    /// Get Rust-specific code actions
    fn get_rust_code_actions(
        &self,
        content: &str,
        range: Range,
        uri: &tower_lsp::lsp_types::Url,
    ) -> Result<Vec<CodeAction>, String> {
        let mut actions = Vec::new();
        let lines: Vec<&str> = content.lines().collect();

        let start_line = range.start.line as usize;
        let end_line = range.end.line as usize;

        if start_line < lines.len() && end_line < lines.len() {
            let selected_text: String = lines[start_line..=end_line].join("\n");

            // Add error handling for unwrap()
            if selected_text.contains(".unwrap()") {
                let converted = selected_text.replace(".unwrap()", ".expect(\"TODO: handle error\")");

                let mut edit_map = HashMap::new();
                edit_map.insert(uri.clone(), vec![TextEdit {
                    range,
                    new_text: converted,
                }]);

                actions.push(CodeAction {
                    title: "Replace unwrap() with expect()".to_string(),
                    kind: Some(CodeActionKind::QUICKFIX),
                    diagnostics: None,
                    edit: Some(WorkspaceEdit {
                        changes: Some(edit_map),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(true),
                    disabled: None,
                    data: None,
                });
            }
        }

        Ok(actions)
    }

    /// Get generic code actions that work for all languages
    fn get_generic_code_actions(
        &self,
        content: &str,
        range: Range,
        uri: &tower_lsp::lsp_types::Url,
    ) -> Result<Vec<CodeAction>, String> {
        let mut actions = Vec::new();
        let lines: Vec<&str> = content.lines().collect();

        let start_line = range.start.line as usize;
        let end_line = range.end.line as usize;

        if start_line < lines.len() && end_line < lines.len() {
            let selected_text: String = lines[start_line..=end_line].join("\n");

            // Sort lines action
            if selected_text.lines().count() > 1 {
                let mut sorted_lines: Vec<&str> = selected_text.lines().collect();
                sorted_lines.sort();
                let sorted_text = sorted_lines.join("\n");

                let mut edit_map = HashMap::new();
                edit_map.insert(uri.clone(), vec![TextEdit {
                    range,
                    new_text: sorted_text,
                }]);

                actions.push(CodeAction {
                    title: "Sort Lines".to_string(),
                    kind: Some(CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
                    diagnostics: None,
                    edit: Some(WorkspaceEdit {
                        changes: Some(edit_map),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(false),
                    disabled: None,
                    data: None,
                });
            }

            // Remove trailing whitespace
            if selected_text.lines().any(|line| line.ends_with(' ') || line.ends_with('\t')) {
                let cleaned_text = selected_text
                    .lines()
                    .map(|line| line.trim_end())
                    .collect::<Vec<_>>()
                    .join("\n");

                let mut edit_map = HashMap::new();
                edit_map.insert(uri.clone(), vec![TextEdit {
                    range,
                    new_text: cleaned_text,
                }]);

                actions.push(CodeAction {
                    title: "Remove Trailing Whitespace".to_string(),
                    kind: Some(CodeActionKind::SOURCE_FIX_ALL),
                    diagnostics: None,
                    edit: Some(WorkspaceEdit {
                        changes: Some(edit_map),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(false),
                    disabled: None,
                    data: None,
                });
            }
        }

        Ok(actions)
    }
}
